package online.filter;

import java.io.IOException;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.logging.log4j.LogManager;

import core.config.Factory;
import core.exception.PhysicalException;
import core.util.ArrayUtil;

/**
 * フィルタ共通
 *
 * @author Tadashi Nakayama
 */
public final class FilterUtil {

	/** キー */
	private static final String CLAZZ = FilterUtil.class.getName();

	/** コンテキストPATH */
	private static final String CONTEXT_PATH = CLAZZ + ".CONTEXT";
	/** リクエストPATH */
	private static final String REQUEST_PATH = CLAZZ + ".REQUEST_PATH";
	/** リクエストURI */
	private static final String REQUEST_URI = CLAZZ + ".REQUEST_URI";
	/** クエリ文字列 */
	private static final String REQUEST_QUERY = CLAZZ + ".REQUEST_QUERY";
	/** レスポンスPATH */
	private static final String RESPONSE_PATH = CLAZZ + ".RESPONSE_PATH";
	/** レスポンスURI */
	private static final String RESPONSE_URI = CLAZZ + ".RESPONSE_URI";
	/** クエリ文字列 */
	private static final String RESPONSE_QUERY = CLAZZ + ".RESPONSE_QUERY";
	/** リダイレクト */
	private static final String REDIRECT_URL = CLAZZ + ".REDIRECT_URL";
	/** リダイレクトなし */
	private static final String NO_REDIRECT = CLAZZ + ".NO_REDIRECT";

	/** セションタイプ */
	private static final String TAG_TYPE = CLAZZ + ".TYPE";
	/** タグ ADDED */
	private static final String TAG_ADDED = CLAZZ + ".ADDED";
	/** タグ REMOVED */
	private static final String TAG_REMOVED = CLAZZ + ".REMOVED";

	/** タグ SID */
	private static final String TAG_SID = "SID";

	/**
	 * コンストラクタ
	 */
	private FilterUtil() {
		throw new AssertionError();
	}

	/**
	 * Viewフォーワド確認
	 * @param request リクエスト
	 * @return Viewフォワードの場合 true を返す。
	 */
	public static boolean isView(final HttpServletRequest request) {
		String path = toPlainURI(request.getRequestURI());
		return path.endsWith(".jsp") || path.endsWith(".jspx");
	}

	/**
	 * クエリ文字列キー値集合化
	 * @param query クエリ文字列
	 * @return クエリ文字列キー値集合
	 */
	public static Set<String> toParameterKeySet(final String query) {
		return Stream.of(Objects.toString(query, "").split("&")).
				map(v -> v.split("=", 2)[0]).
				filter(s -> !s.isEmpty()).
				collect(Collectors.toSet());
	}

	/**
	 * クエリ文字列マップ化
	 * @param query クエリ文字列
	 * @param encoding エンコーディング
	 * @return マップ
	 */
	public static Map<String, String[]> toParameterMap(final String query, final String encoding) {
		return Stream.of(Objects.toString(query, "").split("&")).
					map(v -> v.split("=", 2)).
					filter(v -> v.length == 2).
					collect(Collectors.toMap(
					v -> v[0], v -> new String[]{decode(v[1], encoding)}, ArrayUtil::extend));
	}

	/**
	 * デコード
	 * @param val デコード対象
	 * @param encoding エンコーディング
	 * @return デコード文字列
	 */
	private static String decode(final String val, final String encoding) {
		try {
			return URLDecoder.decode(val, encoding);
		} catch (final UnsupportedEncodingException ex) {
			throw new PhysicalException(ex);
		}
	}

	/**
	 * キー値をクエリ文字列から除外
	 * @param query クエリ文字列
	 * @param keys キー値
	 * @return 除外後文字列
	 */
	public static String stripParameter(final String query, final String... keys) {
		return Stream.of(Objects.toString(query, "").split("&")).
				filter(qry -> !isTarget(qry, keys)).
				collect(Collectors.joining("&"));
	}

	/**
	 * 対象判断
	 * @param qry 文字列
	 * @param keys キー値
	 * @return qryがキー値で開始している場合 true を返す。
	 */
	private static boolean isTarget(final String qry, final String... keys) {
		return Stream.of(keys != null ? keys : new String[0]).
				map(key -> key + "=").anyMatch(qry::startsWith);
	}

	/**
	 * クエリ文字列取得
	 * @param request リクエストオブジェクト
	 * @return クエリ文字列
	 */
	public static String getQueryString(final HttpServletRequest request) {
		if (isInclude(request)) {
			return String.class.cast(request.getAttribute("javax.servlet.include.query_string"));
		}
		return request.getQueryString();
	}

	/**
	 * リクエストURI取得
	 * @param request リクエストオブジェクト
	 * @return リクエストURI
	 */
	public static String getRequestURI(final HttpServletRequest request) {
		if (isInclude(request)) {
			return String.class.cast(request.getAttribute("javax.servlet.include.request_uri"));
		}
		return request.getRequestURI();
	}

	/**
	 * サーブレットパス取得
	 * @param request リクエストオブジェクト
	 * @return サーブレットパス
	 */
	public static String getServletPath(final HttpServletRequest request) {
		if (isInclude(request)) {
			return String.class.cast(request.getAttribute("javax.servlet.include.servlet_path"));
		}
		return request.getServletPath();
	}

	/**
	 * インクルード判断
	 * @param request リクエストオブジェクト
	 * @return インクルードの場合 true を返す。
	 */
	public static boolean isInclude(final HttpServletRequest request) {
		return request.getAttribute("javax.servlet.include.request_uri") != null;
	}

	/**
	 * jsessionidなしURI
	 * @param val URI
	 * @return jsessionidなしURI
	 */
	public static String toPlainURI(final String val) {
		int loc = val.indexOf(';');
		return 0 <= loc ? val.substring(0, loc) : val;
	}

	/**
	 * GETメソッド判断
	 * @param method メソッド
	 * @return GETの場合 true を返す。
	 */
	public static boolean isGetMethod(final String method) {
		return "GET".equalsIgnoreCase(method) || "HEAD".equalsIgnoreCase(method);
	}

	/**
	 * POSTメソッド判断
	 * @param method メソッド
	 * @return POSTの場合 true を返す。
	 */
	public static boolean isPostMethod(final String method) {
		return "POST".equalsIgnoreCase(method);
	}

	/**
	 * コンテキストPATH取得
	 * @param request リクエスト
	 * @return コンテキストPATH
	 */
	public static String getContextPath(final HttpServletRequest request) {
		return String.class.cast(request.getAttribute(CONTEXT_PATH));
	}

	/**
	 * リクエストPATH取得
	 * @param request リクエスト
	 * @return リクエストPATH
	 */
	public static String getRequestPath(final HttpServletRequest request) {
		return String.class.cast(request.getAttribute(REQUEST_PATH));
	}

	/**
	 * レスポンスPATH取得
	 * @param request レスポンス
	 * @return レスポンスPATH
	 */
	public static String getResponsePath(final HttpServletRequest request) {
		return String.class.cast(request.getAttribute(RESPONSE_PATH));
	}

	/**
	 * リクエストURI取得
	 * @param request リクエスト
	 * @return リクエストURI
	 */
	public static String getRequestUri(final HttpServletRequest request) {
		return String.class.cast(request.getAttribute(REQUEST_URI));
	}

	/**
	 * レスポンスURI取得
	 * @param request レスポンス
	 * @return レスポンスURI
	 */
	public static String getResponseUri(final HttpServletRequest request) {
		return String.class.cast(request.getAttribute(RESPONSE_URI));
	}

	/**
	 * リクエストクエリ文字列取得
	 * @param request リクエスト
	 * @return リクエストクエリ文字列
	 */
	public static String getRequestQuery(final HttpServletRequest request) {
		return String.class.cast(request.getAttribute(REQUEST_QUERY));
	}

	/**
	 * レスポンスクエリ文字列取得
	 * @param request リクエスト
	 * @return レスポンスクエリ文字列
	 */
	public static String getResponseQuery(final HttpServletRequest request) {
		return String.class.cast(request.getAttribute(RESPONSE_QUERY));
	}

	/**
	 * リダイレクトなし設定
	 * @param request リクエスト
	 */
	public static void setNoRedirect(final HttpServletRequest request) {
		request.setAttribute(NO_REDIRECT, NO_REDIRECT);
	}

	/**
	 * SIDをAttributeに設定
	 * @param request リクエストオブジェクト
	 * @param obj 設定オブジェクト
	 * @return 設定した場合 true を返す。
	 */
	public static boolean setSessionAttribute(
					final HttpServletRequest request, final Serializable obj) {
		String sid = getAttributeSid(request);
		if (sid != null) {
			if (request.getAttribute(sid) == null) {
				setSessionAdded(request);
			}
			request.setAttribute(sid, obj);
			return true;
		}
		return false;
	}

	/**
	 * Attribute設定オブジェクト取得
	 * @param <T> Type
	 * @param request リクエスト
	 * @return Attribute設定オブジェクト
	 */
	public static <T> T getSessionAttribute(final HttpServletRequest request) {
		String sid = getAttributeSid(request);
		return sid != null ? Factory.cast(request.getAttribute(sid)) : null;
	}

	/**
	 * アクションセション存在確認
	 * @param request リクエストオブジェクト
	 * @return 存在した場合 true を返す。
	 */
	public static boolean hasSessionAttribute(final HttpServletRequest request) {
		String sid = getAttributeSid(request);
		return sid != null && request.getAttribute(sid) != null;
	}

	/**
	 * 保存消去
	 * @param request リクエストオブジェクト
	 */
	public static void removeSessionAttribute(final HttpServletRequest request) {
		String sid = getAttributeSid(request);
		if (sid != null) {
			request.removeAttribute(sid);
		}
		setSessionRemoved(request);
	}

	/**
	 * SIDをパラメタから取得
	 * @param request リクエストオブジェクト
	 * @return SID
	 */
	public static String getParameterSid(final HttpServletRequest request) {
		return request.getParameter(TAG_SID);
	}

	/**
	 * URLのクエリ文字列内からSIDを削除する。
	 *
	 * @param query GETのパラメタクエリ
	 * @return 削除後文字列
	 */
	public static String removeSid(final String query) {
		return stripParameter(query, TAG_SID);
	}

	/**
	 * SIDをAttributeから取得
	 * @param request リクエストオブジェクト
	 * @return SID
	 */
	static String getAttributeSid(final HttpServletRequest request) {
		return String.class.cast(request.getAttribute(TAG_SID));
	}

	/**
	 * SIDをAttributeに設定
	 * @param request リクエストオブジェクト
	 * @param sid SID
	 */
	static void setAttributeSid(final HttpServletRequest request, final String sid) {
		request.setAttribute(TAG_SID, sid);
	}

	/**
	 * SIDをAttributeから削除
	 * @param request リクエストオブジェクト
	 */
	static void removeAttributeSid(final HttpServletRequest request) {
		request.removeAttribute(TAG_SID);
	}

	/**
	 * 開始確認
	 * @param request リクエストオブジェクト
	 * @return 開始された場合 true を返す。
	 */
	static boolean isSessionAdded(final HttpServletRequest request) {
		return request.getAttribute(TAG_ADDED) != null;
	}

	/**
	 * 開始設定
	 * @param request リクエストオブジェクト
	 */
	static void setSessionAdded(final HttpServletRequest request) {
		request.setAttribute(TAG_ADDED, TAG_ADDED);
	}

	/**
	 * 削除確認
	 * @param request リクエストオブジェクト
	 * @return 削除された場合 true を返す。
	 */
	static boolean isSessionRemoved(final HttpServletRequest request) {
		return request.getAttribute(TAG_REMOVED) != null;
	}

	/**
	 * 削除設定
	 * @param request リクエストオブジェクト
	 */
	static void setSessionRemoved(final HttpServletRequest request) {
		request.setAttribute(TAG_REMOVED, TAG_REMOVED);
	}

	/**
	 * 多重セション判断
	 * @param request リクエストオブジェクト
	 * @return 多重セション時 true を返す。
	 */
	static boolean isMultipleSession(final HttpServletRequest request) {
		return "multiple".equals(request.getAttribute(TAG_TYPE));
	}

	/**
	 * セションタイプ設定
	 * @param sid SID
	 * @param multiple multipleフラグ
	 * @param request リクエスト
	 */
	static void setSessionType(final String sid,
					final HttpServletRequest request, final boolean multiple) {
		if (sid != null) {
			if (multiple) {
				request.setAttribute(TAG_TYPE, "multiple");
			} else {
				request.setAttribute(TAG_TYPE, "single");
			}
		} else {
			request.setAttribute(TAG_TYPE, "none");
		}
	}

	/**
	 * リダイレクト確認
	 * @param request リクエスト
	 * @return リダイレクトの場合 true を返す。
	 */
	static boolean isNoRedirect(final HttpServletRequest request) {
		return request.getAttribute(NO_REDIRECT) != null;
	}

	/**
	 * リダイレクトURL取得
	 * @param request リクエスト
	 * @return リダイレクトURL
	 */
	static String getRedirect(final HttpServletRequest request) {
		return String.class.cast(request.getAttribute(REDIRECT_URL));
	}

	/**
	 * 記録
	 * @param request リクエスト
	 * @param location URL
	 */
	static void setRedirect(final HttpServletRequest request, final String location) {
		request.setAttribute(REDIRECT_URL, location);
	}

	/**
	 * 記録
	 * @param request リクエスト
	 */
	static void setContext(final HttpServletRequest request) {
		request.setAttribute(CONTEXT_PATH, request.getContextPath());
	}

	/**
	 * 記録
	 * @param request リクエスト
	 */
	static void setRequest(final HttpServletRequest request) {
		request.setAttribute(REQUEST_PATH, request.getRequestURI().substring(
						request.getContextPath().length() + "/".length()));
		request.setAttribute(REQUEST_URI, request.getRequestURI());
		request.setAttribute(REQUEST_QUERY, request.getQueryString());
	}

	/**
	 * 記録
	 * @param request リクエスト
	 */
	static void setResponse(final HttpServletRequest request) {
		request.setAttribute(RESPONSE_PATH, request.getRequestURI().substring(
						request.getContextPath().length() + "/".length()));
		request.setAttribute(RESPONSE_URI, request.getRequestURI());
		request.setAttribute(RESPONSE_QUERY, request.getQueryString());
	}

	/**
	 * エラー送信
	 * @param response レスポンス
	 */
	static void sendError(final HttpServletResponse response) {
		try {
			if (!response.isCommitted()) {
				response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
				response.flushBuffer();
			}
		} catch (final IOException ex) {
			LogManager.getLogger().info(ex.getMessage());
		}
	}

	/**
	 * リダイレクト
	 * @param response レスポンス
	 * @param location URL
	 */
	static void redirect(final HttpServletResponse response, final String location) {
		try {
			response.reset();
			response.resetBuffer();
			response.setStatus(HttpServletResponse.SC_SEE_OTHER);
			response.setHeader("Location", response.encodeRedirectURL(location));
			response.setContentLength(0);
			response.flushBuffer();
		} catch (final IOException ex) {
			LogManager.getLogger().info(ex.getMessage());
		}
	}

	/**
	 * Location取得
	 *
	 * @param request リクエスト
	 * @return Location
	 */
	static String getRequestLocation(final HttpServletRequest request) {
		String ret = getRequestUri(request);
		String qry = getRequestQuery(request);
		return Objects.toString(qry, "").isEmpty() ? ret : ret + "?" + qry;
	}

	/**
	 * Location取得
	 *
	 * @param request リクエスト
	 * @return Location
	 */
	static String getResponsetLocation(final HttpServletRequest request) {
		String uri = getResponseUri(request);
		String qry = getResponseQuery(request);
		return Objects.toString(qry, "").isEmpty() ? uri : uri + "?" + qry;
	}

	/**
	 * Location取得
	 *
	 * @param request リクエスト
	 * @return Location
	 */
	static String getSidLocation(final HttpServletRequest request) {
		return getRequestUri(request) + toSidQuery(getRequestQuery(request), request);
	}

	/**
	 * sid付きクエリ文字列取得
	 * @param val クエリ文字列
	 * @param request リクエストオブジェクト
	 * @return アクションフォワード
	 */
	private static String toSidQuery(final String val, final HttpServletRequest request) {
		StringBuilder query = new StringBuilder();
		String sid = getAttributeSid(request);
		if (!Objects.toString(sid, "").isEmpty()) {
			query.append("?").append(TAG_SID).append("=").append(sid);
		}
		String qry = removeSid(val);
		if (!Objects.toString(qry, "").isEmpty()) {
			if (0 < query.length()) {
				query.append("&");
			} else if (!qry.startsWith("?")) {
				query.append("?");
			}
			query.append(qry);
		}
		return query.toString();
	}
}
